package common.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;


/**
 * 反射工具类
 * @author 张明
 */
@SuppressWarnings("rawtypes")
public class BeanUtil {
	
	/**
	 * 简单类型对象集合
	 */ 
	private static Class[] primitives = new Class[]{
			Short.class,
			Byte.class,
			Integer.class,
			Float.class,
			Double.class,
			Long.class,
			Boolean.class,
			String.class,
			Collection.class,
			Map.class,
			Timestamp.class
	};
	
	/**
	 * 设置简单类型对象集合，在此集合内的元素会被认为是简单类型
	 * @param primitives 简单类型class集合
	 */
	public static void setPrimitives(Class[] primitives) {
		BeanUtil.primitives = primitives;
	}
	
	public static <T> T fromMap(Map<String, Object> map, Class<T> clazz){
		return new BeanMap() {
			
			@SuppressWarnings("hiding")
			public <T> T fromMap(Map<String, Object> map, Class<T> clazz) {
				T bean = newInstanceBean(clazz);
				for (String fieldName : ToolUtil.getAttributes(Arrays.asList(BeanUtil.getBeanFields(clazz)), "name", String.class)){
					if (map.containsKey(fieldName)){
						setBeanField(bean, fieldName, map.get(fieldName));
					}
				}
				return bean;
			}
		}.fromMap(map, clazz);
		
	}
	
	
	public static <T> T fromMap(Map<String, Object> map, Class<T> clazz, BeanMap beanMap){
		return beanMap.fromMap(map, clazz);
	}
	
	/**
	 * 将bean解析为map(递归解析)
	 * @param bean bean
	 * @return     包含了属性名属性值的bean键值对映射
	 */
	public static Map<String, Object> parseBean(Object bean){
		return parseBean(bean, ToolUtil.getAttributes(Arrays.asList(getBeanFields(bean.getClass())), "name", String.class));
	}
	
	/**
	 * 将bean解析为map(递归解析)
	 * @param bean       bean
	 * @param fieldNames 需要解析的属性名
	 * @return           包含了属性名属性值的bean键值对映射
	 */
	public static Map<String, Object> parseBean(Object bean, String[] fieldNames){
		Map<String, Object> beanMap = new HashMap<String, Object>();
		for (String fieldName : fieldNames){
			String fieldsName = getBeanField(bean.getClass(), fieldName).getName();
			String getMethodName = "get"+fieldsName.substring(0,1).toUpperCase()+fieldsName.substring(1,fieldsName.length()); 
			
			Method getMethod = getBeanMethod(bean.getClass(), getMethodName, new Class[0]);
			
			try {
				Object retVal = getMethod.invoke(bean, new Object[0]);
				
				beanMap.put(fieldName, ToolUtil.isNullOrEmpty(retVal) || isPrimitive(retVal.getClass()) 
						? retVal 
						: parseBean(retVal, null == retVal 
								? new String[0] 
								: ToolUtil.getAttributes(Arrays.asList(getBeanFields(retVal.getClass())), "name", String.class)));
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			}
//			Object value = getBeanField(bean, fieldName, Object.class);
//			beanMap.put(fieldName, ToolUtil.isNullOrEmpty(value) || isPrimitive(value.getClass()) 
//						? value 
//						: parseBean(value, null == value 
//								? new String[0] 
//								: ToolUtil.getAttributes(Arrays.asList(getBeanFields(value.getClass())), "name", String.class)));
		}
		return beanMap;
	}
	
	/**
	 * 将bean解析为map(递归解析)
	 * @param bean       bean
	 * @param fieldNames 需要解析的属性名
	 * @param valueType  返回map中的value类型，必须是同一类型
	 * @return           包含了属性名属性值的bean键值对映射
	 */
	public static <T> Map<String, T> parseBean(Object bean, String[] fieldNames, Class<T> valueType){
		Map<String, Object> map = parseBean(bean, fieldNames);
		Map<String, T> result = new HashMap<String, T>();
		
		for (String key : map.keySet()){
			Object value = map.get(key);
			if (!ToolUtil.isNullOrEmpty(value)){
				result.put(key, valueType.cast(value));
			}
		}
		
		return result;
	}
	
	/**
	 * 调用默认构造器实例化bean <b>这里所指的构造器必须是public的<b>
	 * @param <T> 泛型参数
	 * @param beanCls bean的class
	 * @return        调用默认构造器后的bean实体
	 * @throws RuntimeException 
	 * <p>当试图创建的实例是一个接口或者抽象类而没有明确指向具体实现时抛出</p>
	 * <p>当试图创建实例时但当前正在执行的方法无法访问指定类、字段、方法或构造方法的定义时(比如该类没有定义默认构造器，或者企图访问一个非public的构造器)</p>
	 */
	public static <T> T newInstanceBean(Class<T> beanCls){
		try {
			return beanCls.newInstance();
		} catch (InstantiationException e){
			throw new RuntimeException(e.getMessage());
		} catch (IllegalAccessException e){
			throw new RuntimeException(e.getMessage());
		}
	}
	
	/**
	 * 调用指定构造器实例化bean <b>这里所指的构造器必须是public的<b>
	 * @param <T> 泛型参数
	 * @param beanCls        bean的class
	 * @param initParamTypes 构造器的参数类型数组
	 * @param initParams     构造器的参数数组
	 * @return 调用指定构造器后的bean
	 * @throws RuntimeException
	 * <p>当试图创建的实例时反射执行指定构造器时由构造器内部程序抛出异常时抛出</p> 
	 * <p>当试图创建的实例是一个接口或者抽象类而没有明确指向具体实现时抛出</p>
	 * <p>当根据指定的参数类型数组无法找到指定的类存在此构造器定义时抛出</p>
	 * <p>当试图创建实例时但当前正在执行的方法无法访问指定类、字段、方法或构造方法的定义时(比如该类没有定义默认构造器，或者企图访问一个非public的构造器)</p>
	 */
	public static <T> T newInstanceBean(Class<T> beanCls, Class[] initParamTypes, Object[] initParams){
		try {
			return beanCls.getConstructor(initParamTypes).newInstance(initParams);
		} catch (InvocationTargetException e){
			throw new RuntimeException(e.getMessage());
		} catch (InstantiationException e){
			throw new RuntimeException(e.getMessage());
		} catch (NoSuchMethodException e){
			throw new RuntimeException(e.getMessage());
		} catch (IllegalAccessException e){
			throw new RuntimeException(e.getMessage());
		}
	}
	
	/**
	 * 获取Bean的属性对象
	 * @param beanCls   bean的class
	 * @param fieldName 字段名
	 * @return          字段对象
	 * @throws RuntimeException
	 * <p>当指定的class不存在具有fieldName的字段名时抛出</p> 
	 */
	public static Field getBeanField(Class beanCls, String fieldName){
		try {
			return beanCls.getField(fieldName);
		} catch (NoSuchFieldException e){
			try {
				Field beanField = beanCls.getDeclaredField(fieldName);
				if (! Modifier.isPublic(beanField.getModifiers())){
					beanField.setAccessible(true);
				}
				return beanField;
			} catch (NoSuchFieldException ex){
				throw new RuntimeException(e.getMessage());
			}
		}
	}
	
	/**
	 * 获取bean的主键属性
	 * @param <T>      泛型参数(主键的annotation类型)
	 * @param beanCls  需要获取的bean的class
	 * @param idCls    主键的类型class(必须是annotation的子类)
	 * @return         返回代表主键属性的Field
	 */
	public static <T extends Annotation> Field getBeanIdField(Class beanCls, Class<T> idCls){
		for (Field field : getBeanFields(beanCls)){
			if (! ToolUtil.isNullOrEmpty(field.getAnnotation(idCls))){
				return field;
			}
		}
		return null;
	}

	/**
	 * 获取bean属性值
	 * @param <T> 泛型参数
	 * @param bean      bean
	 * @param fieldName 字段名
	 * @param fieldType 字段类型
	 * @return          属性值
	 * @throws RuntimeException
	 * <p>当视图访问的字段为私有而没有设置相应的访问权限时抛出</p>
	 */
	public static <T> T getBeanField(Object bean, String fieldName, Class<T> fieldType){
		try {
			return fieldType.cast(getBeanField(bean.getClass(), fieldName).get(bean));
		} catch (IllegalAccessException e){
			throw new RuntimeException(e.getMessage());
		}
	}
	
	/**
	 * 设置bean属性值
	 * @param <T>        泛型参数
	 * @param bean       bean
	 * @param fieldName  字段名 
	 * @param fieldValue 字段值
	 * @throws RuntimeException
	 * <p>当视图访问的字段为私有而没有设置相应的访问权限时抛出</p>
	 * <p>当视图访问的字段为为final时抛出</p>
	 * 		   ClassCastException
	 * <p>当fieldValue的类型与对应的fieldName所代表的字段类型不兼容是抛出</p>
	 * 		   NullPointerException
	 * <p>当bean为null时抛出</p>
	 */
	public static <T> void setBeanField(Object bean, String fieldName, T fieldValue){
		try {
			if (null == bean){
				throw new NullPointerException("无法对null进行反射属性设置操作！");
			}
			
			Field beanField = getBeanField(bean.getClass(), fieldName);
			
			beanField.set(bean, fieldValue);
		} catch (IllegalAccessException e){
			throw new RuntimeException(e.getMessage());
		}
	}
	
	/**
	 * 反射调用方法
	 * @param <T> 泛型参数
	 * @param <R> 泛型参数(限制返回值类型)
	 * @param beanCls    定义方法的class
	 * @param bean       调用方法的实体(如果是静态方法此参数可以为null)
	 * @param methodName 方法名
	 * @param paramTypes 方法参数类型数组
	 * @param params     调用方法的参数数组(如果是无参方法此参数可以为null)
	 * @param returnType 返回值类型
	 * @return           调用方法后的返回值如果是void或返回值为null返回null
	 * @throws RuntimeException
	 * <p>当由指定的方法参数类型数组和方法名无法在该类或接口下找到对应的方法时抛出</p> 
	 * <p>当反射调用方法时在方法内部抛出异常时抛出</p>
	 */
	public static <T, R> R doBeanMethod(Class<T> beanCls, T bean, String methodName, Class[] paramTypes, Object[] params, Class<R> returnType){
		try {
			Method beanMethod = getBeanMethod(beanCls, methodName, paramTypes);
			
			Object returnValue = beanMethod.invoke(bean, params);
			
			return ToolUtil.isNullOrEmpty(returnValue) ? null : returnType.cast(returnValue);
		} catch (InvocationTargetException e){
			throw new RuntimeException(e.getMessage());
		} catch (IllegalAccessException e){
			throw new RuntimeException(e.getMessage());
		}
	}
	
	/**
	 * 获取bean方法
	 * @param <T> 泛型参数
	 * @param beanCls    定义方法的class
	 * @param methodName 方法名
	 * @param paramTypes 方法参数类型数组
	 * @return           对应的Method对象
	 * @throws RuntimeException
	 * <p>当由指定的方法参数类型数组和方法名无法在该类或接口下找到对应的方法时抛出</p> 
	 */
	public static <T> Method getBeanMethod(Class<T> beanCls, String methodName, Class[] paramTypes){
		try {
			return beanCls.getMethod(methodName, paramTypes);
		} catch (NoSuchMethodException e){
			try {
				Method beanMethod = beanCls.getDeclaredMethod(methodName, paramTypes);
				if (! Modifier.isPublic(beanMethod.getModifiers())){
					beanMethod.setAccessible(true);
				}
				return beanMethod;
			} catch (NoSuchMethodException ex){
				throw new RuntimeException(e.getMessage());
			}
		
		}
	}
	
	/**
	 * 获取bean所有方法
	 * @param beanCls bean的class
	 * @return        具有该bean所有方法数组
	 */
	public static Method[] getBeanMethods(Class beanCls){
		return ToolUtil.arrayMerge(Method.class, beanCls.getDeclaredMethods(),beanCls.getMethods());
	}
	
	/**
	 * 获取以prex开头的所有方法
	 * @param beanCls bean的class
	 * @param prex    方法名的前缀
	 * @return        获取以prex开头的所有方法
	 */
	public static Method[] getBeanMethods(Class beanCls, String prex){
		Method[] allMethods = getBeanMethods(beanCls);
		
		List<Method> methods = new Vector<Method>();
		for (Method method : allMethods){
			if (method.getName().startsWith(prex)){
				methods.add(method);
			}
		}
		
		return ToolUtil.collectionToArray(methods, Method.class);
	}
	
	/**
	 * 获取bean所有属性
	 * @param beanCls bean的class
	 * @return        具有该bean所有属性数组
	 */
	public static Field[] getBeanFields(Class beanCls){
		return ToolUtil.arrayMerge(Field.class, beanCls.getDeclaredFields(), beanCls.getFields());
	}

	/**
	 * 是否是简单类型
	 * @param type 需要判断的class
	 * @return     当满足下列条件时认为是简单类型
	 * <p>1.满足isPrimitive或isArray返回值为true时</p>
	 * <p>2.满足指定类型可以转换为简单类型对象集合内任一类型时</p>
	 */
	public static boolean isPrimitive(Class type){
		if (type.isPrimitive()){
			return true;
		}
		if (type.isArray()){
			return true;
		}
		
		return isTypeof(type, primitives);
	}
	
	/**
	 * 是否是某一或某几种类型
	 * @param type  需要判断的class
	 * @param types 比较的class数组
	 * @return <p>满足指定类型可以转换为types对象集合内任一类型时为true</p>
	 */
	@SuppressWarnings("unchecked")
	public static boolean isTypeof(Class type, Class[] types){
		for (Class priClass : types){
			if(priClass.isAssignableFrom(type)){
				return true;
			}
		}
		return false;
	}
	
	/**
	 * 通用equals
	 * @param bean1                           实体1
	 * @param bean2                           实体2
	 * @param beanType       实体类型
	 * @param equalFieldName 比较的字段名
	 * @return               若实体1的equalFieldName字段值equeals实体2的字段值equalFieldName则返回true，或两者都为null时返回true
	 */
	public static <T> boolean isBeanEquals(T bean1, T bean2, Class<T> beanType, String equalFieldName){
		if (ToolUtil.isNullOrEmpty(bean1)){
			return ToolUtil.isNullOrEmpty(bean2);
		}
		
		return getBeanField(bean1, equalFieldName, Object.class).equals(getBeanField(bean2, equalFieldName, Object.class));
	}
	
	public interface BeanMap {
		<T> T fromMap(Map<String, Object> map, Class<T> clazz);
	}
}